該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。
上一個章節我們講了兩個在 Vue 裡面做動態的方式,今天來說另外兩個做法。
GSAP 是一個功能強大,且 API 簡單,可以使用在一般的網頁或是canvas上面的動畫函式庫,因為它是為了專門做動畫所誕生的函式庫,所以不管是效能還是它所提供的功能,原比 JQuery 的 animate來的更加強大。
<div class="box-100 bg-red"></div>
.box-100{
width: 100px;
height: 100px;
}
.bg-red{
background-color: red;
}
gsap.to(".box-100", {duration: 1, x: 200, ease: "elastic"});
.to
: 動畫是從起點到終點的執行duration
: 這個動畫執行的秒數x
: 水平移動,綁定的元件最後到達的地方ease
: 動畫速率codepen範例 : https://codepen.io/MikeCheng1208/pen/ExXgQYg
<div class="cube1 box-100 bg-red"></div>
<div class="cube2 box-100 bg-blue"></div>
<button id="btn">click</button>
gsap.timeline({ defaults: { duration: 1, ease:"elastic" }})
.to(".cube1", {x: 100})
.to(".cube2", {y: 100})
.to(".cube2", {x: 100, duration: 0,3})
.timeline
: 控制動畫執行的時間線,可以產生補間影格讓動畫依序執行。先把動畫參數設定完成後,在執行.to
,之後所有的 .to
會全部參照 timeline
設定好的去執行,當然你也可以中途去改變,最後的方塊執行速度會變快,不會照 timeline 設定的 defaults
去執行。
codepen 範例 : https://codepen.io/MikeCheng1208/pen/jOwMZNj
這是我們要使用GSAP來製作的效果,利用卷軸的捲動,來讓我的圖片依序照不同的方向跟旋轉出現。
我們先來看 template 的部分,就是把圖片給放到上面來
<template>
<div id="box">
<div class="pic pic1"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic2"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic3"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic4"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic5"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic6"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic7"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic8"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic9"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic10"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
<div class="pic pic11"><img src="https://source.unsplash.com/user/erondu/240x240"></div>
</div>
</template>
然後css的部分
<style>
#box{
width: 240px;
height: auto;
margin: 0 auto;
padding-top: 250px;
}
.pic{
width: 240px;
height: auto;
overflow: hidden;
background-color: #fff;
margin-bottom: 20px;
opacity: 0;
}
.pic > img{
display: block;
width: 240px;
height: auto;
}
.pic1{
margin-left: -100px;
}
.pic2{
margin-left: 100px;
}
.pic3{
margin-top: 100px;
}
.pic4{
transform: scale(0);
}
.pic5{
width: 0;
}
.pic6{
transform:scaleX(-1.4);
}
.pic7{
margin-left: -100px;
}
.pic8{
margin-left: 100px;
}
.pic9{
transform:scaleY(-1.4);
}
.pic10{
margin-top: 100px;
}
.pic11{
width: 0;
}
</style>
在這邊我把所有圖片的透明度全部設定成 0,然後每一張圖片依序的起始位置就是我動畫要出現的地方,你會看到有 transform: scale(0);
或是 margin-left: -100px;
之類的,所以接下來就是要透過 GSAP 來把它恢復原狀,中間的過程做補間動畫
<script>
import { onMounted, onUnmounted } from 'vue';
import gsap from 'gsap';
export default {
setup(){
let tl = null;
// 抓 document 的 高
const getDocumentHeight = () => {
const body = document.body;
const html = document.documentElement;
return Math.max(
body.offsetHeight,
body.scrollHeight,
html.clientHeight,
html.offsetHeight,
html.scrollHeight
)
}
// 抓卷軸的位置
const documentTop = () => (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop
// 動畫初始化
const gsapInit = () => {
tl = gsap.timeline({defaults: { duration: 1 }});
tl.to(".pic1", {"margin-left": 0, "opacity":1});
tl.to(".pic2", {"margin-left": 0, "opacity":1});
tl.to(".pic3", {"margin-top": 0, "opacity":1});
tl.to(".pic4", {"transform": "scale(1)", "opacity":1, ease: Bounce.easeOut});
tl.to(".pic5", {"width": "240px", "opacity":1});
tl.to(".pic6", {"transform":"scaleX(1)", "opacity":1});
tl.to(".pic7", {"margin-left": 0, "opacity":1});
tl.to(".pic8", {"margin-left": 0, "opacity":1});
tl.to(".pic9", {"transform":"scaleY(1)","opacity":1});
tl.to(".pic10", {"margin-top": 0, "opacity":1});
tl.to(".pic11", {"width": "240px", "opacity":1});
tl.pause();
}
const handleWinScroll = () =>{
const scrollTop = documentTop();
const docHeight = getDocumentHeight();
const winHeight = window.innerHeight;
const scrollPercent = (scrollTop) / (docHeight - winHeight);
tl.progress(scrollPercent);
}
onMounted(()=> {
gsapInit();
window.addEventListener("scroll", handleWinScroll);
})
onUnmounted(()=> {
window.removeEventListener("scroll" ,handleWinScroll);
})
return {}
}
};
</script>
這邊要注意我宣告了一個 tl
但是我並沒有用 ref
或 reactive
包起來,這是因為這個是要給 gsap 的實體存放的,不需要透過 Vue 包起來,所以不需要用 ref
或 reactive
。
tl = gsap.timeline({defaults: { duration: 1 }});
tl.to(".pic1", {"margin-left": 0, "opacity":1});
tl.to(".pic2", {"margin-left": 0, "opacity":1});
tl.to(".pic3", {"margin-top": 0, "opacity":1});
tl.to(".pic4", {"transform": "scale(1)", "opacity":1, ease: Bounce.easeOut});
tl.to(".pic5", {"width": "240px", "opacity":1});
tl.to(".pic6", {"transform":"scaleX(1)", "opacity":1});
tl.to(".pic7", {"margin-left": 0, "opacity":1});
tl.to(".pic8", {"margin-left": 0, "opacity":1});
tl.to(".pic9", {"transform":"scaleY(1)","opacity":1});
tl.to(".pic10", {"margin-top": 0, "opacity":1});
tl.to(".pic11", {"width": "240px", "opacity":1});
tl.pause();
這邊你會看到我依序地把圖片一個一個恢復原狀的位置,所以一開始的時候我會用 CSS 把元件的位置給移開,再透過GSAP給補回來。
這邊要特別注意一下 tl.pause();
這個函式,它是把這個動畫整個先暫停下來,然後在使用 scroll
事件來跑我們動畫的補間進度,透過換算,我們可以得出一個卷軸從最上面滑到最下面的百分比,然後把這個百分比塞入 tl.progress();
這個函式內,就可以依照卷軸百分比來顯示你的圖片。
tl.progress()
: 是百分比是從 0
~ 1
;透過 GSAP 的方式我們可以很簡單的完成這樣子的卷軸控制物件出來的動態,只是要注意,抓取 DOM 的時候需要等 DOM 都 render 到畫面上,所以必須要在 onMounted
的時候再去執行,我也是在 onMounted
才去執行 gsapInit
這個函式的。
codepen範例 : https://codepen.io/MikeCheng1208/pen/JjJRpXJ
我有錄製了一份 GSAP3 的免費教學放在 Youtube 上面,如果對 GSAP 有興趣的可以看一下,或是上 GSAP 的官網看看。
GSAP 官網 : https://greensock.com/gsap/
youtube 連結 : https://www.youtube.com/playlist?list=PLbOfcOk7bN40WfzgRMLEQzkayS8pjWO_e
在上一篇教學中,有提到 transition 有提供動畫的 Lifecycle Hooks,所以我們可以針對每個動畫所執行的階段去做一些操作,不過同樣的我們也可以在這些 Lifecycle Hooks 裡面去做動畫的操作,捨棄掉原本的 CSS,透過第三方動畫工具整合,我們可以做出更多 CSS 難以做到的動畫效果,這邊我一樣選用 GSAP 來做整合,GSAP 可是連 Vue 都指令拿來做文件的範例用的,你說這可有多好用呢~
我們先來看一下 transition 的 Lifecycle Hooks。
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
:css="false"
>
</transition>
@before-enter
:開始的動畫開始前。
@enter
:動畫開始的執行過程。
@after-enter
:開始的動畫結束時。
@enter-cancelled
:開始的動畫被取消。
@before-leave
:離開的動畫開始前。@leave
:離開動畫的執行過程。@after-leave
:離開動畫的結束時。@leave-cancelled
:離開的動畫被取消。v-bind:css
:設定 false
可以讓動畫執行的過程中不會受到 css的影響。關於 Transition 的 Lifecycle Hooks 官方文件 : https://v3.vuejs.org/guide/transitions-enterleave.html#javascript-hooks
其實大部分的 Lifecycle Hooks 就跟我們的 css class 是一樣的,只是多了一兩個東西而已,所以接下來我們就要來看怎麼把 Vue 跟 GSAP 做一個整合。
我們拿上一個章節所用的這個輪播範例來改,我們拿掉了 name
,加上了 Hooks 。
<transition-group
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
:css="false"
>
<img
v-for="(item, idx) in slidList"
v-show="imgIdx === idx"
:key="item.id"
:src="item.src"
/>
</transition-group>
// --------
// ENTERING
// --------
const beforeEnter = (el) => {
gsap.set(el, {
scaleX: 0,
scaleY: 0,
x: 0,
y: 0,
opacity: 0,
})
}
const enter = (el, done) => {
gsap.to(el, {
duration: 1,
scaleX: 1,
scaleY: 1,
opacity: 1,
ease: 'elastic.inOut(1,2, 1)',
onComplete: done
})
}
// --------
// LEAVING
// --------
const leave = (el, done) => {
gsap.to(el, {
duration: 0.7,
scaleX: 3,
scaleY: 0.05,
x: 0,
opacity: 0,
ease: 'elastic.inOut(2.5, 1)'
})
gsap.to(el, {
duration: 0,
delay: 0.5,
onComplete: done
})
}
Lifecycle Hooks 使用的時候會回傳當前執行動畫的 DOM 實體,所以我們就要拿這個實體直接來套入 GSAP 中,就不需要直接寫 class 的名稱在裡面。
首先我們透過 beforeEnter
先用 gsap.set
來設定動畫開始以前的樣式,然後當動畫在執行的時候在改變回來,然後當動畫執行到離開(leave)
的時候,再去做離開的動畫樣式處理,只是要特別注意,在 enter
和 leave
hooks 中,必須要在動畫結束後調用 done
這個函式,不然的話當你動畫還沒有執行完你在執行下一段的時候,補間就會直接完成動畫,沒有中間的動畫效果,所以 GSAP 在動畫完成之後有提供自己的 lifecycle,動畫完成之後執行的 lifecycle 叫 onComplete
,所以我就可以把, enter
和 leave
回傳的done
函式放入 onComplete
去執行它。
codepen 範例 : https://codepen.io/MikeCheng1208/pen/OJgRQBK
關於 Vue 的動畫上面的處理就先告一個段落,其實網路上面也是還有其他的套件或是整合方式,只是在工作上面來說,這四種動畫的處理方式已經可以解決大部分的問題了,我都有附上 codepen 的範例,有興趣的朋友可以自己玩玩看。
Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。
我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc
那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc
Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng